// 
// SoapTrace.Net - A soap message monitoring utility. 
// 
// Copyright (C) 2004-2009 Matthew Ward
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
// 
// Matthew Ward (mrward@users.sourceforge.net)
// 

using SoapTrace.Core.Diagnostics;
using System;
using System.Globalization;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using WinPcap;

namespace SoapTrace.Core.Communication
{
	/// <summary>
	/// Monitors soap data packets using the 
	/// <see cref="WinPcap.NetworkInterface"/> class or the 
	/// <see cref="RawSocket"/> class.
	/// </summary>
	public class SoapPacketMonitor : SoapPacketMonitorBase, IDisposable
	{
		/// <summary>
		/// Disposed flag.
		/// </summary>
		private bool m_Disposed;
		
		/// <summary>
		/// The network interface object that will capture packets.
		/// </summary>
		private NetworkInterface m_NetworkInterface;
		
		/// <summary>
		/// The socket that is used to capture packets if we are not
		/// using WinPcap.
		/// </summary>
		private RawSocket m_RawSocket;
		
		/// <summary>
		/// Flag indicating whether the packet monitor will use WinPcap or 
		/// Raw Sockets.
		/// </summary>
		private bool m_UseWinPcap = true;
		
		/// <summary>
		/// Creates a new instance of the <see cref="SoapPacketMonitor"/>
		/// class.
		/// </summary>
		public SoapPacketMonitor( )
		{
		}
		
		/// <summary>
		/// Disposes this class.
		/// </summary>
		public void Dispose( )
		{
			Dispose( true );

			GC.SuppressFinalize( this );
		}
		
		/// <summary>
		/// Gets or sets whether to use WinPcap for packet capture.  The 
		/// alternative is to use Raw sockets.
		/// </summary>
		public bool UseWinPcap
		{
			get
			{
				return m_UseWinPcap;
			}
			
			set
			{
				m_UseWinPcap = value;
			}
		}
		
		/// <summary>
		/// Starts the packet monitor.
		/// </summary>
		/// <param name="networkInterfaceName">The name of the network
		/// interface to use for monitoring.</param>
		/// <param name="remoteServerIPAddress">The remote server's
		/// IP address that we are interested in monitoring data packets.</param>
		/// <param name="remoteServerPort">The remote server's
		/// port that packets will be sent to.</param>
		public void Start( string networkInterfaceName,
			string remoteServerIPAddress,
		    int remoteServerPort )
		{
			Logger.LogDebug( "Starting packet monitor..." );
			
			// Set packet filter.
			IPAddress IPAddress = GetIPAddress( remoteServerIPAddress );
			
			Logger.LogDebug( String.Concat( "Filter=", IPAddress.ToString( ), ":", remoteServerPort.ToString( ) ) );
			                
			base.SetFilter( IPAddress.ToString( ), remoteServerPort );
			
			if( m_UseWinPcap )
			{
				m_NetworkInterface = new NetworkInterface( );
				m_NetworkInterface.PacketFilter = 
					String.Concat( "tcp port ", remoteServerPort );
				
				Logger.LogDebug( String.Concat( "Packet filter=", m_NetworkInterface.PacketFilter ) );
				
				// Hook up packet capture events.
				
				m_NetworkInterface.PacketReceived += 
					new PacketReceivedEventHandler( NetworkInterfacePacketReceived );
				
				// Open network interface.
				m_NetworkInterface.Open( networkInterfaceName );
			}
			else
			{
				m_RawSocket = new RawSocket( );
				m_RawSocket.PacketReceived += new PacketReceivedEventHandler( RawSocketIPPacketReceived );
				m_RawSocket.Open( networkInterfaceName );
			}
			
			Logger.LogDebug( "Packet monitor started." );
		}
		
		/// <summary>
		/// Stops the packet monitor.
		/// </summary>
		public void Stop( )
		{
			Logger.LogDebug( "Stopping packet monitor..." );
			
			if( m_NetworkInterface != null )
			{
				m_NetworkInterface.Close( );
				m_NetworkInterface = null;
			}
			
			if( m_RawSocket != null )
			{
				m_RawSocket.Close( );
				m_RawSocket = null;
			}
			
			Logger.LogDebug( "Packet monitor stopped." );
		}
		
		/// <summary>
		/// Gets the available network interfaces as determined from Windows
		/// Raw sockets.
		/// </summary>
		/// <returns>A collection of network interface names.</returns>
		public static NetworkInterfaceNameCollection GetRawSocketNetworkInterfaceNames( )
		{
			NetworkInterfaceNameCollection Names = new NetworkInterfaceNameCollection( );
			
			foreach( IPAddress IPAddress in RawSocket.GetLocalHostIPAddresses( ) )
			{
				NetworkInterfaceName Name = new NetworkInterfaceName( );
				Name.Name = IPAddress.ToString( );
				Name.Description = Name.Name;
				
				Names.Add( Name );
			}
			
			return Names;
		}
		
		/// <summary>
		/// Gets the available network interfaces as determined from WinPcap.
		/// </summary>
		/// <returns>A collection of network interface names.</returns>
		public static NetworkInterfaceNameCollection GetWinPcapNetworkInterfaceNames( )
		{
			NetworkInterfaceNameCollection Names = new NetworkInterfaceNameCollection( );

			NetworkInterfaceCollection Interfaces =
				NetworkInterface.GetAllNetworkInterfaces( );
						
			foreach( NetworkInterface Device in Interfaces )
			{
				NetworkInterfaceName Name = new NetworkInterfaceName( );
				Name.Name = Device.Name;
				Name.Description = Device.Description;
				
				Names.Add( Name );
			}

			return Names;
		}
	
		/// <summary>
		/// Logs the bytes transferred.
		/// </summary>
		protected override void BytesTransferred( object sender, TcpCommunicationEventArgs e )
		{
			if( Logger.IsLevelEnabled( LogLevel.Debug ) )
			{
				Logger.LogDebug( String.Concat( "BytesTransferred. Source=", e.SourceAddress.ToString( ),
				                               ", Dest=", e.DestinationAddress.ToString( ) ) );
				Logger.LogDebug( String.Concat( "bytes.length=", e.GetBytes( ).Length ) );
				
				if( e.GetBytes( ).Length > 0 )
				{
					Logger.LogDebug( String.Concat( "bytes=", GetBytesAsString( e.GetBytes( ), e.GetBytes( ).Length ) ) );
				}
			}
			
			base.BytesTransferred( sender, e );
		}
		
		/// <summary>
		/// Logs the bytes transferred.
		/// </summary>
		protected override void MissedConnectionBytesTransferred( object sender, TcpCommunicationEventArgs e )
		{
			if( Logger.IsLevelEnabled( LogLevel.Debug ) )
			{
				Logger.LogDebug( String.Concat( "MissedConnectionBytesTransferred. Source=", e.SourceAddress.ToString( ),
				                               ", Dest=", e.DestinationAddress.ToString( ) ) );
				Logger.LogDebug( String.Concat( "bytes.length=", e.GetBytes( ).Length ) );
				
				if( e.GetBytes( ).Length > 0 )
				{
					Logger.LogDebug( String.Concat( "bytes=", GetBytesAsString( e.GetBytes( ), e.GetBytes( ).Length ) ) );
				}
			}
			
			base.MissedConnectionBytesTransferred( sender, e );
		}		
		
		/// <summary>
		/// Processes packets received by the <see cref="NetworkInterface"/>
		/// object.
		/// </summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">Event argument that contains the packet received by
		/// the <see cref="NetworkInterface"/> object.</param>
		private void NetworkInterfacePacketReceived( object sender, PacketReceivedEventArgs e )
		{
			Logger.LogDebug( "NetworkInterfacePacketReceived" );
			
			base.ProcessPacket( e.Packet );
		}
		
		/// <summary>
		/// Processes <see cref="IPPacket"/>s.
		/// </summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">The event arguments.</param>
		private void RawSocketIPPacketReceived( object sender, PacketReceivedEventArgs e )
		{
			IPPacket IPPacket = e.Packet as IPPacket;
			
			if( IPPacket != null )
			{
				base.ProcessIPPacket( IPPacket );
			}	
		}
		
		/// <summary>
		/// Gets a byte array as a formatted string.
		/// </summary>
		/// <param name="bytes">The byte array.</param>
		/// <param name="length">The length of the byte array.</param>
		/// <returns>A formatted string.</returns>
		private static string GetBytesAsString( byte[ ] bytes, int length )
		{
			string FormattedText = UnicodeEncoding.UTF8.GetString( bytes, 0, length );
			
			FormattedText = FormattedText.Replace( "\n", "\\n" );
			FormattedText = FormattedText.Replace( "\r", "\\r" );
			
			return FormattedText;
		}		
		
		/// <summary>
		/// Disposes managed and unmanaged objects. 
		/// </summary>
		/// <param name="disposing">If <see langword="true"/> the method has been called directly
		/// or indirectly by a user's code. Managed and unmanaged resources
		/// can be disposed.
		/// If disposing equals <see langword="false"/>, the method has been called by the 
		/// runtime from inside the finalizer and you should not reference 
		/// other objects. Only unmanaged resources can be disposed.
		/// </param>
		private void Dispose( bool disposing )
		{
			if( !m_Disposed )
			{
				if( disposing )
				{
					// Dispose managed resources.
					
					Stop( );
				}
	             
				// Clean up unmanaged resources here.
			}

			m_Disposed = true;         
		}	
		
		/// <summary>
		/// Gets the IP address from a host name or ip address string.
		/// </summary>
		/// <param name="address">An IP address or a host name.</param>
		/// <returns>An IP address.</returns>
		private IPAddress GetIPAddress( string address )
		{
			IPAddress IPAddress = null;
			
			try
			{
				IPAddress = IPAddress.Parse( address );
			}
			catch( FormatException )
			{
				IPHostEntry HostEntry = Dns.GetHostEntry( address );
				IPAddress = TcpSocket.GetIPAddress( HostEntry );
			}
			
			return IPAddress;
		}
	}
}
